Blocking and Non-Blocking: Asynchronous Nature of Node.js

Firat Atalay
7 min read1 day ago

Let’s now take a minute to learn about the asynchronous nature of Node.js, which includes absolutely fundamental topics, like synchronous, asynchronous, blocking, and non-blocking code. And all of this will be really important in order to understand everything that’s coming up throughout this section.

So this piece of code that we wrote in the last lecture, to read a file and then, save it’s content into a variable, was in a so-called synchronous way, which simply means that each statement is basically processed one after another, line by line.

In this example, first, the file system module is required, then, the file is read, and then, we log the result to the console. So you see that each line of code basically waits for the result of the previous line. Now, this can become a problem, especially with slow operations, because each line blocks the execution of the rest of the code.And so, we say that synchronous code is also called blocking code because, again, a certain operation can only be executed after the one before has finished. And because of the way Node.js was designed, this turns into a huge problem, as we’ll see in detail in the next slide.

So the solution to this problem in Node is to use asynchronous, non-blocking code. So in asynchronous code, we upload heavy work to basically be worked on in the background. And then, once that work is done, a callback function that we register before is called to handle the result. And during all that time, the rest of the code can still be executing without being blocked by the heavy task, which is now running in the background. So what this means is that we can effectively defer or reaction into the future in order to make the code non-blocking and this is, of course, much better. Makes sense? So, in this example, we use the asynchronous readFile function, which accepts a callback function. This will start reading the file in the background and then, immediately move on to the next statement, printing to the console the string-reading file. So, again, you see, we are not blocking the execution here. Then, when the file is finally completely read, the callback function will be called, and so, the data that was read will then be printed to the console. So that’s quite different from the synchronous version, isn’t it?

Now, the question here is, why does is actually have to be this way? What’s the problem with blocking code execution in Node.js? Or, in other words, why do we actually use callback so many times in Node.js?

Well, let’s find out. And in order to understand these questions, the first thing that we need to understand is the fact that a Node.js process, which is where our application is running, there’s only one single thread. And the thread is just like a set of instructions that is run in the computer’s CPU. So basically, the thread is where our code is actually executed in a machine’s processor. So, remember, Node.js is basically single-threaded and so, for each application, there’s only one thread. That’s just the way Node.js was designed.

Now, what that means is that all the users accessing your application are all using the same thread, so, basically, accessing the same thread. And so, whenever they’re interacting with the application, the code that is run for each user will be executed all in the same thread at the same place in the computer running the application. And that is true no matter if you have five users, like in this diagram, or 5,000 or 5 million. All right?

Now, what this also means is that when one user locks the single thread with synchronous code, like we just saw before, then all other users will have to wait for that execution to finish.

And that might not be a huge problem if you have like five users, but it definitely will for thousands or even millions of users at the same time. So, imagine there’s a user accessing your application and there’s a huge synchronous file read in your code that will take like one second to load. This will mean, of course, that for that one second, all other users will have to wait because the entire execution is blocked for that one second. So if those other users want to do some simple tasks, like logging into your application or just requesting some data, they won’t be able to do so. They will have to wait until the file is finished reading. Only when that happens they will finally be able to perform the simpler tasks, one after another. Now, please note, that this is a very oversimplified version of what really happens behind the scenes of Node.js, which is why we will come back to all of this in the next section and get an even deeper understanding of how Node.js handles asynchronous code under the hood. But at this point, this is enough for you to understand the concept. It’s better to go step-by-step here and not make it too confusing right from the beginning, okay?

Anyway, this is how the situation would play out with synchronous blocking code, which is obviously a terrible experience for your users. And so, it’s really your job as a developer to avoid these kinds of situations by using asynchronous code.

So, for the same situation, we should, of course, use the asynchronous file read function, which instead of blocking the single thread, does the heavy work in the background, where it basically stays until it’s finished reading the data from the file. Of course, we then also register a callback function to be called once the data is available. And in this scenario, all the other users can then perform their tasks in a single thread, one after another, while the file is still being read in the background. Now, once the data is read, our callback function will, of course, get called to be executed in the main single thread in order to process the read data. And that’s it. That’s an overview of how Node.js handles asynchronous behavior in order to implement the non-blocking I/O model that we talked about in the intro lecture, all right?

And I/O simply stands for input-output, which is basically stuff like accessing the file system and handling network requests. This is actually the whole reason why Node.js is completely designed around callbacks, as you will see throughout the course. In other programming languages, like PHP, it works very differently because you get, basically, one new thread for each new user, which is a completely different paradigm and really works completely different. But the creator of Node.js found this model to be the best solution for building highly performant and scalable web applications.

Now, just as a final note here, it’s important to know that, when we use callbacks in our code, that doesn’t automatically make it asynchronous, all right? So, passing functions around into other functions is quite common in JavaScript, but of course, again, that doesn’t make them asynchronous automatically, okay? It only works this way for some functions in the Node API, such as the readFile function and many, many more, as people explore in the future.

And now, just to finish, since we’re talking about asynchronous code here, just one last note about callback functions. So, this callback model that we just discussed, where one function is called once the one before has finished it’s work, can quickly lead to some hard to read and unmanageable code. Just take this example where the second file read depends on the first one, then, the third file read depends on the second one, and finally, we want to use the final data to write a file as a result. That looks quite confusing, right? I mean, it’s gonna work just fine, but it’s just hard to reason about and that is just with four levels deep. Imagine you had like 10 or 20 levels, which is actually not that uncommon. Anyway, this is what we call the callback hell. It’s such a common problem, that it already got its own name. And do you notice this triangular shape here? That’s a very clear sign that you’re in callback hell.

Now, how do we actually escape callback hell?

Well, we can use more advanced tools for handling asynchronous code, like ES6 promises or even better, ES8 async/await.

Now, the model that we just talked about will still be the same. We just have more elegant ways of dealing with the code itself and writing it. And there is a whole optional section of it later in the course on promises and also, async/await, so in case you’re not familiar with them. But for now, we will keep using callbacks because that is what Node originally used and was designed around. And now, with that being said, let’s move on and use this asynchronous model in practice for the first time.